package src;

import src.Base.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Vistor {
    SyntaxWord root;
    SymbolTable curTable = SymbolTable.globelTable;
    SymbolTable pre = null;
    HashMap<String, Symbol> func_symbol;// 函数名——该函数符号
    HashMap<String, SymbolTable> func_Pars;//函数名——参数符号表
    String nowFuncName;//当前处理的函数名

    boolean isLoop;
    //boolean needReturn;
    int base_id;
    public Vistor(SyntaxWord root){
        this.root = root;
        func_symbol = new HashMap<>();
        func_Pars = new HashMap<>();
        this.base_id = 0;
    }
    public void createSymbolTable(){
        traverseTree(root);
    }
    private void traverseTree(SyntaxWord node){
        if(node != null){
            //System.out.println("fH:");
            handle(node);
            //System.out.println("cH:");
            for(SyntaxWord tmp:node.getChildren()){
                traverseTree(tmp);
            }
            if(!node.isEnd()&&node.getType().equals("Block") && !node.isEnd()){
                curTable = curTable.Father;
            }
        }
    }
    private void handle(SyntaxWord syntaxWord){
        if(!syntaxWord.isEnd()){
            //System.out.println("node:"+syntaxWord.getType());
            if(nameCheck(syntaxWord, "FuncDef")){
               FuncDef(syntaxWord);
            }else if(nameCheck(syntaxWord, "Block")){
                Block(syntaxWord);
            } else if (nameCheck(syntaxWord, "VarDef")) {
                VarDef(syntaxWord);
            }else if(nameCheck(syntaxWord, "MainFuncDef")){
                MainDef(syntaxWord);
            }else if(nameCheck(syntaxWord, "LVal")){
                LVal(syntaxWord);
            }else if(nameCheck(syntaxWord, "Stmt")){
                Stmt(syntaxWord);
            } else if (nameCheck(syntaxWord, "ConstDef")) {
                ConstDef(syntaxWord);
            } else if(nameCheck(syntaxWord, "UnaryExp")){
                UnaryExp(syntaxWord);
            }
        }
    }

    private void UnaryExp(SyntaxWord syntaxWord) {
        ArrayList<SyntaxWord> children = syntaxWord.getChildren();
        if(!children.get(0).isEnd()){
            return;
        }
        String f_name = syntaxWord.getChild0Content();
        int line = syntaxWord.getLine();
        //System.out.println(f_name + " "+queryName(f_name));
        if(!queryName(f_name)){
            ErrorLog.instance.addError(line,"c");
            return;
        }
        if(children.size() < 3){ //Ident '(' [FuncRParams] ')' size == 3 || 4
            return;
        }
        //System.out.println(children.size());
        if(!children.get(2).isEnd() && children.get(2).getType().equals("FuncRParams") && !children.get(2).isEnd()){
            SyntaxWord funcRParams = children.get(2);
            int count = 0;
            ArrayList<Symbol> funcParamsList = func_symbol.get(f_name).getParams();
            int param_count = funcParamsList.size();
            for(SyntaxWord tmp:funcRParams.getChildren()){
                if(!tmp.isEnd() && tmp.getType().equals("Exp")){
                    if(tmp.isEnd()){
                        break;
                    }
                    int dim = 0;
                    //String paramName = tmp.getToken().getContent();
                    int paramType;
                    if(count >= funcParamsList.size()){
                        paramType = 0;
                    }else {
                        paramType = funcParamsList.get(count).getType();
                    }

                    int paramDim = -1;
                    if(paramType == -1 || paramType == 0){
                        paramDim = 0;
                    }else{
                        paramDim = paramType;
                    }
                    //Exp -->+  LVal
                    SyntaxWord syntax = tmp;
                    while(syntax.getChildren().size() == 1){
                        syntax = syntax.getChildren().get(0);
                        if(!syntax.isEnd()&&syntax.getType().equals("LVal")){
                            String sym_name = syntax.getChild0Content();
                            Symbol symbol = getSymbolByName(sym_name);
                            if(symbol == null){
                                count++;
                                break;
                            }
                            //System.out.println("handle:"+syntax.getContent());
                            System.out.println(symbol.getType() + " - "+calDim(syntax.getChildren()));
                            dim = symbol.getType() - calDim(syntax.getChildren());
                            break;
                        }
                        //System.exit(-33);
                    }
                    if(!syntax.isEnd() && syntax.getType().equals("LVal")){

                    }else if(!syntax.isEnd() && syntax.getType().equals("UnaryExp")){
                        if(syntax.getChildren().get(0).isEnd()){
                            String sym_name = syntax.getChild0Content();
                            Symbol symbol = getSymbolByName(sym_name);
                            if(symbol == null){
                                count++;
                                continue;
                            }
                            if(symbol.getRetype() != 0){
                                // 0 -> int, 1->void
                                ErrorLog.instance.addError(syntaxWord.getLine(), "e");
                                count++;
                                continue;
                            }
                        }
                        dim = 0;
                    }else{
                        dim = 0;
                    }
                    //System.out.println("dim:" + dim + " rDim:"+ paramDim);
                    if(dim != paramDim){
                        ErrorLog.instance.addError(syntaxWord.getLine(), "e");
                    }
                    count++;
                }
            }
            if(count != param_count){
                ErrorLog.instance.addError(line, "d");
            }
        }
        else{
            if (SymbolTable.globelTable.getSymbol(f_name).getParams().size() != 0){
                ErrorLog.instance.addError(line, "d");
            }
        }
    }

    private int calDim(ArrayList<SyntaxWord> children) {
        int dim = 0;
        //System.out.println("calculate:"+children.get(0).getToken().getContent());
        for (SyntaxWord tmp : children) {
            if (tmp.getToken() != null) {
                if (tmp.getToken().getType().equals("LBRACK")){
                    dim++;
                }
            }
        }
        //System.out.println("res = "+dim);
        return dim;
    }
    private void ConstDef(SyntaxWord syntaxWord) {
        ArrayList<SyntaxWord> children = syntaxWord.getChildren();
        int line = syntaxWord.getLine();
        String name = syntaxWord.getChild0Content();
        //Symbol funcSymbol = curTable;
        if(curTable.getFName() != null && func_Pars.containsKey(curTable.getFName())){
            SymbolTable funcTable = func_Pars.get(curTable.getFName());
            if(funcTable.exist(name)){
                ErrorLog.instance.addError(line, "b");
            }
        }
        if(curTable.exist(name)){
            ErrorLog.instance.addError(line, "b");
        }else{
            Symbol newSymbol = new Symbol(name, getDim(children), 1);
            //System.out.println("ConstDef add sym " + name + " dim:"+getDim(children));
            curTable.addSymbol(name, newSymbol);
        }
    }

    private void Stmt(SyntaxWord syntaxWord) {
        ArrayList<SyntaxWord> children = syntaxWord.getChildren();
        if(children.get(0).isEnd()){
            String token = syntaxWord.getChild0Content();
            if(token.equals("for")) {
                //'for' '('[ForStmt] ';' [Cond] ';' [ForStmt] ')' Stmt
                // 0     1    2       3    4     5     6       7   8
                SyntaxWord stmt = children.get(children.size() - 1);
                // stmt child0 isEnd 或非end type 不是Block时，用block包装后处理
                if(!stmt.isEnd()){
                        if(stmt.getChildren().get(0).isEnd() || !stmt.getChildren().get(0).getType().equals("Block")){
                            children.remove(children.size() - 1);
                            children.add(wrapWithBlock(stmt));
                            stmt = children.get(children.size() - 1);
                        }
                }
                if(!stmt.isEnd() && stmt.getChildren().get(0).getType().equals("Block")){
                    isLoop = true;
                }
                stmt.setLoop(true);
            } else if(token.equals("break") || token.equals("continue")){
                System.out.println(syntaxWord);
                if(!curTable.getIsLoop() && !syntaxWord.getIsLoop()){
                    ErrorLog.instance.addError(syntaxWord.getLine(), "m");
                }
            } else if (token.equals("return")) {
                String funcName = curTable.getFName();
                Symbol funcSymbol = func_symbol.get(funcName);
                if(funcSymbol.getRetype() == 1 && (children.get(1).getToken() == null || !children.get(1).getToken().getContent().equals(";"))){
                    //为void 类型 returnTK 中第1项不为; 则其Token可能为空，Type不为空为AddExp
                    if(children.get(1).getType().equals("Exp")&&!children.get(1).isEnd()){
                        ErrorLog.instance.addError(syntaxWord.getLine(), "f");
                    }
                }
            } else if (token.equals("printf")) {
                //<Stmt> →‘printf’‘(’<FormatString>{,<Exp>}’)’‘;’
                            //0    1      2
                SyntaxWord s = children.get(2);
                int correctCnt = cal5d(s.getToken().getContent());
                int cnt = 0;
                for(SyntaxWord tmp:children){
                    if(tmp.isEnd() && tmp.getToken().getContent().equals(","))cnt++;
                }
                if(cnt != correctCnt){
                    ErrorLog.instance.addError(syntaxWord.getLine(), "l");
                }
            }
        } else{
            if(children.get(0).getType().equals("LVal")){
                SyntaxWord LVal = children.get(0);
                String name = LVal.getChild0Content();
                //System.out.println("HERROR:"+name);
                Symbol symbol = getSymbolByName(name);
                if(symbol != null){
                    if(symbol.getCon() == 1){
                        int tmp = LVal.getLine();
                        ErrorLog.instance.addError(tmp, "h");
                    }
                }
            }
        }
    }

    private SyntaxWord wrapWithBlock(SyntaxWord stmt) {
        SyntaxWord Stmt = new SyntaxWord("Stmt",null);
        SyntaxWord L = new SyntaxWord(null, new Word(-1,WordType.LBRACE.toString(), "{"));
        SyntaxWord R = new SyntaxWord(null, new Word(-1, WordType.RBRACE.toString(), "}"));
        SyntaxWord blockItem = new SyntaxWord("BlockItem", null);
        SyntaxWord block = new SyntaxWord("Block", null);
        blockItem.addChild(stmt);
        block.addChild(L);
        block.addChild(blockItem);
        block.addChild(R);
        Stmt.addChild(block);
        return Stmt;
    }

    private int cal5d(String content) {
        String pattern = "%d";
        Pattern p = Pattern.compile(pattern);
        Matcher m = p.matcher(content);
        int count = 0;
        while (m.find()) {
            count++;
        }
        return count;
    }

    private Symbol getSymbolByName(String name) {
        //System.out.println("check "+name);
        SymbolTable now = curTable;
        while(now.Father != null || now.equals(SymbolTable.globelTable)){
            if(now.exist(name)){
                return now.getSymbol(name);
            }
            if(now.getFName() != null){
                String funName = now.getFName();
                if(func_Pars.containsKey(funName)){
                    if(func_Pars.get(funName).exist(name)){
                        return func_Pars.get(funName).getSymbol(name);
                    }
                }
            }
            now = now.Father;
            if(now == null){
                break;
            }
        }
        return null;
    }

    private void LVal(SyntaxWord syntaxWord) {
        String name = syntaxWord.getChild0Content();
        if(!queryName(name)){
            int line;
            line = syntaxWord.getLine();
            ErrorLog.instance.addError(line, "c");
        }
    }

    private boolean queryName(String name) {
        SymbolTable now = curTable;
        while(now.Father != null || now.equals(SymbolTable.globelTable)){
            if(now.exist(name)){
                return true;
            }
            // 当前符号表有函数名，即现在为函数的符号表时
            if(now.getFName() != null){
                String func_Name = now.getFName();
                if(func_Pars.containsKey(func_Name)){// 存在 函数名与该函数的参数符号表间的映射
                    SymbolTable paramTable = func_Pars.get(func_Name);
                    if(paramTable.exist(name))
                        return true;
                }
            }
            now = now.getFather();
            if(now == null){
                break;
            }
        }
        return false;
    }

    private void VarDef(SyntaxWord syntaxWord) {
        ArrayList<SyntaxWord> children = syntaxWord.getChildren();
        String name = syntaxWord.getChild0Content();
        String funcName = curTable.getFName();
        SymbolTable parTable = func_Pars.get(funcName);
        if(func_Pars.containsKey(funcName) && curTable.getFName() != null && parTable.exist(name)){
            //函数参数名重复
            ErrorLog.instance.addError(children.get(0).getToken().getLine(), "b");
        }
        if(curTable.exist(name)){//重复变量名
            ErrorLog.instance.addError(children.get(0).getToken().getLine(), "b");
        }else{
            //    private int type;//0 -> a, 1 -> a[], 2 -> a[][], -1 -> func
            //    private  int con;//1 -> const, 0 -> var
            System.out.println("add Symbol:"+name + " dim:" + getDim(syntaxWord.getChildren()));
            Symbol tmp = new Symbol(name, getDim(syntaxWord.getChildren()), 0);
            curTable.addSymbol(name, tmp);
            //System.out.println("VarDef add Symbol " + name);
            // type = 0 -> 普通变量， con = 0 -> var
        }
    }

    private void MainDef(SyntaxWord syntaxWord){
        Symbol symbol = new Symbol("main", -1, -1, 0, 0, null);
        nowFuncName = "main";
        func_symbol.put("main", symbol);
        ArrayList<SyntaxWord> children = syntaxWord.getChildren();
        int line;
        SyntaxWord block = children.get(children.size() - 1);
        int childNum = block.getChildren().size();
        SyntaxWord blockItem = block.getChildren().get(childNum - 2);
        line = block.getChildren().get(childNum - 1).getToken().getLine();
        if(blockItem.getType() == null){
            ErrorLog.instance.addError(line, "g");
            return;
        }
        if(blockItem.getType().equals("BlockItem")){
            if(!blockItem.isEnd()){
                SyntaxWord stmt = blockItem.getChildren().get(0);
                if(errorGInBlockItem(stmt)){
                    ErrorLog.instance.addError(line, "g");
                }
            }
        }else{
            ErrorLog.instance.addError(line, "g");
        }
    }


    private void Block(SyntaxWord syntaxWord) {
        SymbolTable table = new SymbolTable();
        table.Father = curTable;
        table.setIsLoop(isLoop);
        if (table.Father!=null && table.Father.getIsLoop()){
            table.setIsLoop(true);
        }
        table.setFuncName(nowFuncName);
        //pre = curTable;
        curTable = table;
        isLoop = false;
    }

    private void FuncDef(SyntaxWord syntaxWord){
        ArrayList<SyntaxWord> children = syntaxWord.getChildren();
        Word Ident = children.get(1).getToken();
        String type = children.get(0).getChild0Content();
        String name = Ident.getContent();
        nowFuncName = name;
        int line = Ident.getLine();
        SymbolTable table = curTable;
        if(table.exist(name)){
            ErrorLog.instance.addError(line, "b");
        }else{
            int retype = 1;
            if(type.equals("int")){
                retype = 0;
            }
            ArrayList<Symbol> parameters = new ArrayList<>();
            for(SyntaxWord child:children){
                if(child.getType() != null) {
                    if (child.getType().equals("FuncFParams") && !child.isEnd()) {
                        SymbolTable paramsTable = new SymbolTable();
                        ArrayList<SyntaxWord> params = child.getChildren();
                        for (SyntaxWord param : params) {
                            if(param.getType() == null){
                                continue;
                            }
                            if (param.getType().equals("FuncFParam") && !param.isEnd()) {
                                String paramName = param.getChildren().get(1).getToken().getContent();
                                if (!paramsTable.exist(paramName)) {
                                    int symbolType = getDim(param.getChildren());
                                    Symbol symbol = new Symbol(paramName, symbolType, -1);
                                    paramsTable.addSymbol(paramName, symbol);
                                    parameters.add(symbol);
                                } else {
                                    int error_line = param.getChildren().get(1).getToken().getLine();
                                    ErrorLog.instance.addError(error_line, "b");
                                }
                            }
                        }
                        func_Pars.put(nowFuncName, paramsTable);
                    }
                }
            }
            Symbol symbol = new Symbol(name, -1,0,retype, parameters.size(), parameters);
            func_symbol.put(name, symbol);
            curTable.addSymbol(name, symbol);
            System.out.println("FuncDef add Sym " + name);
            if (type.equals("int")){
                int error_line;
                SyntaxWord block = children.get(children.size() - 1);
                int childNum = block.getChildren().size();
                nodePrint(block);
                SyntaxWord blockItem = block.getChildren().get(childNum - 2);
                error_line = block.getChildren().get(childNum - 1).getToken().getLine();
                if(blockItem.getType() == null){
                    if(blockItem.getToken().getContent().equals("{")){
                        ErrorLog.instance.addError(error_line, "g");
                        return;
                    }
                }
                if(blockItem.getType().equals("BlockItem")){
                    if(!blockItem.isEnd()){
                        SyntaxWord stmt = blockItem.getChildren().get(0);
                        if(errorGInBlockItem(stmt)){
                            ErrorLog.instance.addError(error_line, "g");
                        }
                    }
                }else{
                    ErrorLog.instance.addError(error_line, "g");
                }
            }
        }//
    }
    private boolean nameCheck(SyntaxWord word, String name){
        return word.getType().equals(name);
    }
    private int getDim(ArrayList<SyntaxWord> list){
        int num = 0;
        for(SyntaxWord tmp:list){
            if(tmp.isEnd()&&tmp.getToken().getContent().equals("[")){
                if (tmp.getToken() != null){
                    num++;
                }
            }
        }
        return num;
    }

    private boolean errorGInBlockItem(SyntaxWord stmt) {
        if(!stmt.getType().equals("Stmt")){
            return true;
        }
        return !(stmt.getChildren().get(0).isEnd() && stmt.getChild0Content().equals("return"));
    }

    private void nodePrint(SyntaxWord temp){
        System.out.println("-------------------\nnode:");
        ArrayList<SyntaxWord> list = temp.getChildren();
        if(temp.isEnd()){
            System.out.println(temp.getToken().getType());
            return;
        }
        for(SyntaxWord syntaxWord:list){
            if(syntaxWord.getType() == null){
                System.out.println(syntaxWord.getToken().getType());
            }else{
                System.out.println("<"+syntaxWord.getType()+">");
            }
        }
        System.out.println("-------------------------");
    }
}
